package generation;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;

import astnode.DeclarationNode;
import astnode.FunctionDeclarationNode;
import astnode.VariableDeclarationNode;

import common.LogPrinter;

/**
 * @author Yunke Li, Boshi Li
 */

public class CodeGenerator {
	LogPrinter logPrinter = null;	
	PrintStream output = null;
	CodeTable codeTable = new CodeTable();
	HashMap< String , Integer > labMap = new HashMap< String , Integer >(); 
	ArrayList<DeclarationNode> ASTRoot = null;
	GenerationVisitor visitor = null;
	
	public CodeGenerator( ArrayList<DeclarationNode> ASTRoot, PrintStream output, LogPrinter logPrinter, ArrayList<String> spellingTable ) {
		this.ASTRoot = ASTRoot;
		this.logPrinter = logPrinter;
		this.output = output;
		visitor = new GenerationVisitor( codeTable, logPrinter, spellingTable );
	}
	
	/**
	 * analysis extended AST and product quadruples
	 */
	public void analyse(){
		traversalAST();
		logPrinter.println("Symbolic Quadruaple:");
		transIntoExecutable();
		logPrinter.println("Executable Quarduaple:");
		outputQuard();
	}
	
	/**
	 * traversal AST to generate symbolic quadruple
	 */
	private void traversalAST(){
		int glbalSize = 0;
		for( int i =0; i<ASTRoot.size(); i++ ){
			DeclarationNode dec = ASTRoot.get(i);
			if( dec instanceof VariableDeclarationNode ){
				dec.accept( visitor );
				glbalSize += ((VariableDeclarationNode) dec).getTotalSizes();
			}
		}
		productCode("start", String.valueOf(glbalSize), "", "");
		String rval = visitor.mallocSpace(1);
		productCode("rval", "", "", rval);
		productCode("call", "main", "", "");
		productCode("hlt", "", "", "");		
		for( int i =0; i<ASTRoot.size(); i++ ){
			DeclarationNode dec = ASTRoot.get(i);
			if( dec instanceof FunctionDeclarationNode ){
				dec.accept( visitor );
			}
		}
	}
	
	/**
	 * translate from symbolic quadruple into executable quadruple
	 */
	private void transIntoExecutable(){
		
		for( int i=0; i<codeTable.size(); i++ ){
			//print symbolic quadruple	
			logPrinter.println( codeTable.get(i).toString() );
			if ( codeTable.get(i).getOperator().equals("lab") ) {
				String lab = codeTable.get(i).getResult();
				int labIndex = codeTable.getLabResprentNum(i);
				labMap.put( lab , labIndex);
			}else if( codeTable.get(i).getOperator().equals("fun")){
				String name = codeTable.get(i).getAddress1();
				int funIndex = codeTable.get(i).getIndex();
				labMap.put( name , funIndex );
			}
		}
		
		for(int i=0; i<codeTable.size(); i++ ){
			CodeTableElement element = codeTable.get(i);
			String eleOprator = element.getOperator();
			if( eleOprator.equals("call")){
				int funIndex = labMap.get( element.getAddress1() );
				element.setAddress1("@"+funIndex);
			}else if( eleOprator.equals("iff") || eleOprator.equals("goto") || eleOprator.equals("ift")){
				int labIndex = labMap.get( element.getResult() );
				if( labIndex == -1 ){
					//if element get result -1, it represent the there is no code below lab
					//it just happened when function return before it ends, like
					//if (a ) return; else return;
					//so we don't need to go to the lab, since it will be returned
					//we just trick it by set operator lab
					element.setOperator("lab");
				}else{
					element.setResult( "@" + labIndex );
				}				
			}
		}
	}
	
	/**
	 * output quadruple
	 */
	private void outputQuard(){
		for( int i=0; i<codeTable.size() ; i++ ){
			if( codeTable.get(i).getOperator().equals("lab"))	
				continue;
			
			CodeTableElement element = codeTable.get(i);
			logPrinter.println( String.format("%d:%s", element.getIndex(),element.getQuad()) );
			output.println( element.getQuad() );
		}
	}
	
	/**
	 * product symbolic quadruple into code table
	 * @param operator	
	 * @param address1
	 * @param address2
	 * @param result
	 */
	private void productCode( String operator, String address1, String address2 , String result ){
		codeTable.add( operator, address1, address2, result );
		logPrinter.println( "Product Codes: " + operator + "," + address1 + "," + address2 + "," + result);
	}	
}
